home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Software Vault: The Gold Collection
/
Software Vault - The Gold Collection (American Databankers) (1993).ISO
/
cdr51
/
fastpack.zip
/
FASTPACK.ASM
< prev
next >
Wrap
Assembly Source File
|
1993-04-05
|
33KB
|
847 lines
; Program: Fastpack.asm - source code for a fast .dbf file pack routine.
; Author: R. Russell Freeland / modified by Jay Parsons.
; Notice: This code is a modification of the Fastpack.asm code found in
; dBASE POWER: Building and Using Programming Tools, by P. L.
; Olympia, R. Russell Freeland and Randy Wallin, published and
; copyright by Ashton-Tate Corporation, 1988.
; Mods: Modified by Jay Parsons, December, 1989 as follows:
; 1) Only one write to screen per buffer.
; 2) Pack-then-write algorithm implemented.
; (Above improve speed for small records.)
; 3) Header of packed file updated to current date.
; 4) Packed file given original extension.
; 5) Assembly option for runnable .exe and .com files added.
; 6) Assembly option for 80386 and related instructions added.
; 7) Command/assembly option for no screen writing added.
; 8) Command/assembly option for no backup file added.
; 9) Numerous insignificant changes due to programmer style.
;----------------------------------------------------------------------------
; Assembly Options
false equ 0 ; definitions required
true equ -1 ; for options
; Change the false in the following line to true before assembly if you
; will be debugging the program within dBASE by starting dBASE with the
; DOS command "DEBUG dBASE."
debugging equ false
; Change the false in the following line to true before assembly
; to link into a runnable .exe file convertible into a .com file.
; Must remain false to create a file convertible to a dBASE .bin file.
DOSexe equ false
; Change the false in the following line to true before assembly to
; assemble using instructions available only on an 80386 processor.
; The resulting program will not run on any earlier processor.
for386 equ false
if for386
.386
endif
; Make one of the three following choices true before assembly:
; noterse - program will display number of records copied on the screen.
; allterse - program will display nothing on the screen.
; terseopt - program accepts /T switch for terse, otherwise is noterse.
; allterse is faster than noterse; terseopt code is longer.
allterse equ false
noterse equ false
terseopt equ true
; Make one of the three following choices true before assembly:
; nobak - program will pack the original file.
; allbak - program will create a packed file and leave original as .bak.
; bakopt - program accepts /N switch for nobak, otherwise is allbak.
; nobak is considerably faster than allbak and does not require disk
; space for the new file, but risks data loss; bakopt code is longer.
allbak equ false
nobak equ false
bakopt equ true
; File buffer size (applies only if DOSexe is false, to .bin files).
; A larger buffer will allow the program to run more quickly, but will
; use scarce dBASE memory. The buffer size should always be at least
; the size of the maximum possible .dbf file header, 8K+1 (8,193) bytes,
; unless it can be assured that all actual file headers will be shorter
; than the buffer size chosen.
buffsize equ 12288
;----------------------------------------------------------------------------
; IF YOU CHANGE THE CODE BELOW THIS LINE, YOU ARE ON YOUR OWN
;----------------------------------------------------------------------------
; Selectors and Macros
if allterse
tcond equ 1
else
if noterse
tcond equ 0
else
tcond equ -1
endif
endif
if allbak
bcond equ 1
else
if nobak
bcond equ 0
else
bcond equ -1
endif
endif
if tcond LT 0
eitheropt equ true
else
if bcond LT 0
eitheropt equ true
else
eitheropt equ false
endif
endif
BTEST MACRO jump ; test bakup=true, cond. jump
if bcond LT 0
cmp bakup,true
jump
endif
ENDM
CLOSE MACRO handle ; close file "handle"
LOCAL notopen
mov bx,handle
cmp bx,0 ; don't close stdin
jz notopen
mov ax,3e00h
int 21h
notopen:
ENDM
;----------------------------------------------------------------------------
; Program Definitions
; Return codes to dBASE showing result of program
ok_flag equ 1 ; no problems
header_err equ 2 ; header error (no such file?)
write_err equ 3 ; error in writing new file
read_err equ 5 ; error in reading old one
; Other definitions
eof_mark equ 26
del_mark equ '*'
dot equ '.'
slash equ '/'
space equ ' '
fnamelen equ 127 ; length of a path and filename
maxheader equ 255*32+33 ; 255 field descriptors of 32
; bytes, 32 for the main header
; and 0dh. Equals 8K+1.
; First 12 bytes of the .dbf header (first 12 bytes of the 32-byte
; main header preceding the field descriptor array) give the lengths we
; need of the header and records, plus the date and count we must change.
dbf_header STRUC
memo_ind db ? ; .dbf version and .dbt flag
year db ? ; date of last update
month db ? ; ditto
day db ? ; ditto
if for386
rec_count dd ?
else
rec_count dw 2 dup(?) ; reccount() as doubleword
endif
header_len dw ? ; length of total header
rec_len dw ? ; length of a data record
dbf_header ENDS
year_offset equ 1 ; bytes from start to year
date_n_count equ 7 ; bytes in date and count
;----------------------------------------------------------------------------
; Code and Data
if for386
CODESEG Segment USE16 'CODE'
else
CODESEG Segment 'CODE'
endif
assume CS:CODESEG
if DOSexe
org 100h
; This routine is not part of the fastpack program but a wraparound to
; create an .exe file which may be run from DOS. See Assembly Options.
start: mov bx,80h ; argument portion of PSP
mov al,byte ptr [bx] ; command count
cmp al,0 ; if any
jz noarg
cbw ; as a word
inc bx ; skip space
mov di,ax
add di,bx ; point di to the 0dh
xor al,al
stosb ; replace 0dh with ASCIIZ
call fastpack
noarg: mov ah,4ch ; return to DOS with code in al
int 21h
;---------------------------------------------------------------------------
; Fastpack - main routine of the program.
; Sets up filenames, opens files, calls subroutines, closes files.
fastpack proc near ; if DOSexe, near for .COM
else
fastpack proc far ; else far for a dBASE .BIN
start: ; and start with it
endif ; DOSexe
jmp begin ; usual jump over data
; data storage area
ife DOSexe
old_fname db fnamelen dup(0) ; path & name of source file
new_fname db fnamelen dup(0) ; & target file
endif
even
old_handle dw ? ; their DOS handles
new_handle dw ?
if bcond LT 1
read_ptr dw 2 dup(?)
write_ptr dw 2 dup(?)
endif
dbf dbf_header <> ; storage of the header info
read_len dw ? ; # bytes in use; done if 0
ife DOSexe
rw_buff db buffsize dup(?) ; must be > maximum header size
endif
db_extension db '.DBF',0
new_extension db '$$$',0
old_extension db 'BAK',0
if tcond LT 0
terse db ? ; true if no screen writes
endif
if bcond LT 0
bakup db ? ; true if backup file
endif
count db 8 dup(?) ; storage for # of records
count_msg db ' records copied',13,'$' ; and balance of message
begin:
if debugging
int 3 ; pass control to DEBUG
endif
; actual start of program code
push ds ; save argument pointer
push bx
mov bp,sp ; and stack frame
cld
les di,[bp] ; point es:di to argument
mov cx,-1 ; can't go this far
mov al,space ; strip leading spaces
repe scasb
dec di ; point to first non-space
mov bx,di ; and save pointer
xor al,al ; look for the ASCIIZ
mov cx,-1
repne scasb
not cx ; and convert the count
mov ax,cs ; destination is our segment
mov es,ax
mov di,offset old_fname ; copy the argument
mov si,bx ; skipping spaces
rep movsb ; including the ASCIIZ
mov ds,ax ; no more need for argument
assume ds:codeseg,es:codeseg ; nor for other segments
call parse ; parse argument
mov old_handle,0 ; flag for no file open
mov new_handle,0
call read_header ; read the file header
mov al,header_err
jc shorthop
if bcond LT 1 ; (0) /these numbers are
if for386 ; (1) /nesting levels of ifs
mov eax,dword ptr read_ptr
mov dword ptr write_ptr,eax
else
mov ax,read_ptr
mov write_ptr,ax
mov ax,read_ptr+2
mov write_ptr+2,ax
endif ; for386 (1)
endif ; bcond LT 1 (0)
if DOSexe
mov ax,offset rw_buff ; .exe buffer size is entire
not ax ; segment less code
sub ax,80h ; and room for stack
else
mov ax,size rw_buff ; .bin buffer must be internal
endif
xor dx,dx
mov cx,dbf.rec_len
div cx ; discard partial record
mul cx ; keep the whole records
cmp ax,0 ; if a whole one will fit
jnz buff_ok
mov al,header_err
shorthop: jmp short finish
buff_ok: mov read_len,ax ; and save length in use
BTEST <jnz nonew>
if bcond
mov ah,3ch ; create the new file
mov dx,offset new_fname
xor cx,cx
int 21h
jnc makenew ; go on if no error
mov cl,write_err
jmp short finish
endif ; bcond
if bcond LT 1
nonew: mov ax,old_handle
mov new_handle,ax
jmp short rw
endif ; bcond LT 1
if bcond
makenew: mov bx,ax
mov new_handle,bx ; save new handle
mov cx,dbf.header_len ; length of the header
mov dx,offset rw_buff
mov ax,4000h ; write header to new file
int 21h
mov al,write_err ; quit on error
jc finish
endif ; bcond
rw: call readwrite ; now do the data
jc finish
BTEST <jz doheader>
if bcond LT 1
mov bx,old_handle
xor cx,cx ; dx doesn't matter
mov ah,40h ; truncate file at pointer
int 21h
endif
doheader: call fix_header ; fix up the header
jc finish
mov al,ok_flag ; sign of success
finish: mov dx,ax ; save success/error code
CLOSE old_handle ; close the open files
BTEST <jnz finish2>
if bcond
CLOSE new_handle
endif
finish2: mov ax,dx ; no file-closing errors
cmp al,1
jnz alldone
BTEST <jnz alldone>
if bcond
call rename ; change names if two files
endif
alldone: mov sp,bp ; reset stack for safety
pop bx
pop ds ; restore argument pointers
mov [bx],al ; write return code and done
ret
fastpack endp
;---------------------------------------------------------------------------
; parse - parse argument for filename and switches.
; Capitalizes filename at old_fname. Sets terse and bakup.
; If bakup is false, puts name of new file into new_fname.
parse proc near
mov si,offset old_fname
xor dx,dx ; a pair of zeroes
if tcond LT 0
mov terse,false ; reset terse and bakup
endif
if bcond LT 0
mov bakup,true
endif
caploop: lodsb
cmp al,0 ; at end yet?
jz gotchars
if eitheropt ; (0)
cmp al,slash ; signal for a switch?
jnz getchar2
cmp dh,true
jz testopt
mov dh,true
mov di,si
testopt: mov al,[si]
and al,5fh ; capitalize
if tcond LT 0 ; (1)
cmp al,'T' ; terse?
jnz getbak
mov terse,true ; make it so
jmp short caploop
getbak:
endif ; tcond LT 0 (1)
if bcond LT 0 ; (1)
cmp al,'N' ; no backup?
jnz caploop ; if not, ignore it
mov bakup,false
jmp short caploop
endif ; bcond LT 0 (1)
endif ; eitheropt (0)
getchar2: cmp al,dot
jnz getchar3
cmp dl,true ; already got one?
jz caploop ; then ignore it
cmp dh,true
jz caploop ; or if past filename
mov dl,true ; flag for dot
mov bx,si ; point past the dot
jmp short caploop
getchar3: cmp al,space
jnz capit
if eitheropt ; (0)
cmp dh,true ; got name end already?
jz caploop ; then ignore it
mov dh,true
mov di,si ; point past filename
jmp short caploop
else
jmp short gotchars
endif ; eitheropt (0)
capit: cmp al,'a'
jb caploop
and byte ptr [si-1],5fh ; capitalize it
jmp short caploop
gotchars:
if eitheropt
cmp dh,true ; found any spaces or slash?
jz checkz
endif
mov di,si ; if not, this is past name end
checkz: mov byte ptr [di-1],0 ; add ASCIIZ in case needed
cmp dl,true ; already got a dot?
jz gotdot ; then move on
mov bx,di ; pointer will be past dot
dec di
mov si,offset db_extension ; add .dbf,0
mov cx,5
rep movsb
gotdot:
BTEST <jnz parsedone>
if bcond
mov cx,bx ; pointer past the dot
mov si,offset old_fname
sub cx,si ; cx now length thru dot
mov di,offset new_fname
rep movsb ; copy old name to new
mov si,offset new_extension
mov cx,4
rep movsb ; and add new extension
endif
parsedone: ret
parse endp
;---------------------------------------------------------------------------
; read_header - read the header of the old file, save key data
; returns with carry set in case of i/o error
read_header proc near
mov dx,offset old_fname ; open the old file
BTEST <jz read_open>
if bcond LT 1
mov ax,3d02h ; for read or write
endif
if bcond
jmp short any_open
read_open: mov ax,3d00h ; for read only
endif
any_open: int 21h
jc readfin
mov bx,ax
mov old_handle,bx ; save handle
mov dx,offset rw_buff
mov cx,maxheader ; read maximum header length
mov ax,3f00h
int 21h
jc readfin
mov di,offset dbf ; copy first 12 bytes to dbf
mov cx,size dbf_header
mov si,dx
rep movsb
mov dx,dbf.header_len ; cx=0 and bx=handle already
if bcond LT 1
mov read_ptr,dx ; save pointer
mov read_ptr+2,cx
endif
mov ax,4200h ; point past header for read
int 21h
readfin: ret
read_header endp
;---------------------------------------------------------------------------
; readwrite - main processing. Read a full buffer of data,
; pack it to remove deleted records, write it, print progress and
; continue until done.
; Speed in "readnext" loop is important to performance of the program.
readwrite proc near
mov di,offset count ; clear count buffer
if for386
mov dword ptr dbf.rec_count,0
mov eax,' ' ; four spaces
stosd ; twice
stosd
else
mov word ptr dbf.rec_count,0 ; initialize record count
mov word ptr dbf.rec_count+2,0
mov ax,' ' ; two spaces
mov cx,4 ; times 4
rep stosw
endif
readnext: mov bx,old_handle ; from old file
mov cx,read_len
mov dx,offset rw_buff ; read them into the buffer
mov ax,3f00h
int 21h
jnc readok
readerr: mov al,read_err
ret
readok: mov bx,dx ; start of data
add dx,ax ; end of data
cmp ax,cx ; got full buffer?
jz gotfull ; okay
mov read_len,0 ; else flag for all done
mov si,dx
cmp byte ptr [si-1],eof_mark ; got an eof?
jnz nowpack ; if not, carry on
dec dx ; else drop the eof_mark
if bcond LT 1
jmp short nowpack
endif
gotfull:
BTEST <jz nowpack>
if bcond LT 1 ; (0)
if for386 ; (1)
movzx eax,cx ; else reset for next read
add dword ptr read_ptr,eax
else
add word ptr read_ptr,ax
adc word ptr read_ptr+2,0
endif ; for386 (1)
endif ; bcond LT 1 (0)
nowpack: call packdata ; pack the buffer
call writebuff ; and write it
jc writerr
xor dx,dx ; ax holds bytes written
div dbf.rec_len ; divide for records written
if for386
cwde ; records cannot exceed 32,000
add eax,dbf.rec_count ; add previous totals
mov dbf.rec_count,eax ; and save the new
else
xor dx,dx ; ignore remainder (eof_mark)
add ax,dbf.rec_count ; add previous totals
adc dx,dbf.rec_count+2
mov dbf.rec_count,ax ; save new totals
mov dbf.rec_count+2,dx
endif
if tcond LT 0
cmp byte ptr terse,true
jz readon
endif
if tcond LT 1
mov bx,offset count_msg ; point to the message
call convertit ; convert total to ASCII
mov dx,offset count
mov ah,9 ; print it
int 21h
endif
readon: cmp read_len,0
jz rwdone
BTEST <jz readnext>
if bcond LT 1
mov bx,old_handle
mov dx,read_ptr ; offset
mov cx,read_ptr+2
mov ax,4200h ; from start of file
int 21h ; go there
endif
jmp readnext
writerr: mov al,write_err
rwdone: ret
readwrite endp
;---------------------------------------------------------------------------
; packdata - pack the undeleted data records, held in the buffer from
; (ds: and es:) bx to dx, into the lowest part of it.
; Destroys ax,cx,si; returns with di pointing to end of packed data.
; Uses value held in dbf.rec_len and definition of del_mark.
packdata proc near ; must be near--see below
mov cx,dbf.rec_len
mov di,dx ; presumed end of good data
xor si,si ; nothing to write
mov ax,del_mark ; no-deletes-yet and mark
sub bx,cx ; back up
packon: add bx,cx ; move up a record
cmp bx,dx ; done?
jae packend ; if so, jump
packtest: cmp byte ptr [bx],al ; got a deleted one?
je packdel ; jump for that too
cmp ah,al ; is this first good record?
jnz packon ; if not, continue
xor ah,ah ; flag as got good one
mov si,bx ; and mark start of good block
jmp short packon
packdel: cmp ah,al ; is this first deleted rec?
jz packon ; no, continue
mov ah,al ; set flag to show deletes
cmp di,dx ; does di point to deleteds?
jae packmark ; if not, jump
call packit ; else pack
jmp short packon ; and carry on
packmark: mov di,bx ; mark end of good block
jmp short packon ; and on
packend: cmp ah,al ; do we have good records?
jz packed ; if not, done
; Warning - because the following code is both called and fallen into
; at the end, the procedure must be "near" to avoid stack crash.
packit: cmp si,di ; anything to pack?
jbe packed
push cx
mov cx,bx
sub cx,si
rep movsb
pop cx
packed: ret
packdata endp
;---------------------------------------------------------------------------
; writebuff - write the portion of rw_buff from its start to di to the
; open file of which the handle is in new_handle. If read_len=0, add
; eof_mark at the end of the data and write it too.
; Destroys bx,cx,dx; returns with ax holding # bytes written.
writebuff proc near
mov bx,new_handle
BTEST <jz writehere>
if bcond LT 1
mov dx,write_ptr
mov cx,write_ptr+2 ; else adjust pointer
mov ax,4200h
int 21h
endif
writehere: mov cx,di
mov dx,offset rw_buff
sub cx,dx
cmp read_len,0 ; at end of file?
jnz writeit ; if not, just write
inc cx
mov byte ptr [di],eof_mark ; else add the mark
writeit: jcxz writefin
mov ax,4000h
int 21h
jc writefin ; error?
cmp ax,cx ; partial write is error too
if bcond LT 1 ; (0)
jb writefin
BTEST <jz writefin>
if for386 ; (1)
movzx eax,cx
add dword ptr write_ptr,eax
else
add write_ptr,cx
adc write_ptr+2,0
endif ; for386 (1)
endif ; bcond LT 1 (0)
writefin: ret
writebuff endp
;---------------------------------------------------------------------------
; fix_header - write system date, and record count from dbf.year, etc.,
; to header of file whose handle is in new_handle. Uses definitions
; year_offset, date_n_count and write_err.
; Destroys bx,cx,dx; returns with ax holding # bytes.
fix_header proc near
mov ah,2ah ; get date
int 21h
sub cx,1900 ; adjust year
mov dbf.year,cl ; and save
xchg dh,dl ; put day in dh, month in dl
mov word ptr dbf.month,dx ; and save back-words
mov bx,new_handle
xor cx,cx
mov dx,year_offset ; point past the first byte
mov ax,4200h ; adjust file pointer
int 21h
mov dx,offset dbf.year ; write from dbf storage
mov cx,date_n_count ; 3 bytes date, 4 # of records
mov ah,40h ; write them to file
int 21h
mov al,write_err
ret
fix_header endp
if bcond
;---------------------------------------------------------------------------
; rename - rename old file as .BAK and give temporary file name of
; old file.
rename proc near
mov di,offset new_fname ; replace '$$$' with 'BAK'
mov al,dot
mov cx,fnamelen
repne scasb
push di
mov si,offset old_extension
mov cx,2
rep movsw
mov dx,offset new_fname ; delete existing .BAK file
mov ah,41h
int 21h
mov dx,offset old_fname ; rename old file as .BAK
mov di,offset new_fname
mov ah,56h
int 21h
pop di ; get back pointer
mov si,offset new_extension
mov cx,2
rep movsw ; and bring back the $$$
mov dx,offset new_fname
mov di,offset old_fname
mov ah,56h ; rename new file to old name
int 21h
mov al,ok_flag
ret
rename endp
endif
if tcond LT 1
;---------------------------------------------------------------------------
; convertit - convert number in dx:ax, or in eax if for386 is nonzero,
; into right-justified ASCII string ending at ds:[bx].
; Destroys ax,cx,dx,si or eax,ecx,edx, and returns with [bx] pointing
; to first digit.
convertit proc near
if for386
mov ecx,10
nextdiv: xor edx,edx
div ecx
else
mov cx,10
nextdiv: mov si,ax ; save low word of value
mov ax,dx ; divide high word only
xor dx,dx
div cx ; dx now holds high remainder
xchg si,ax ; save hi quotient/get low word
div cx ; divide hi rem:low word by 10
endif
dec bx ; back up
or dl,30h ; convert low rem to ASCII
mov [bx],dl ; and place it in string
if for386
or eax,eax
else
mov dx,si ; get hi quotient back
or si,ax ; test for done
endif
jnz nextdiv
ret
convertit endp
endif
if DOSexe
old_fname equ $ ; path & name of source file
new_fname equ old_fname+fnamelen ; & target file
rw_buff equ new_fname+fnamelen
endif
CODESEG ends
end start